package com.sooth.common.log.aspect;

import cn.hutool.core.lang.Dict;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import com.sooth.common.core.utils.ServletUtils;
import com.sooth.common.core.utils.SpringUtils;
import com.sooth.common.core.utils.StringUtils;
import com.sooth.common.json.utils.JsonUtils;
import com.sooth.common.log.annotation.Log;
import com.sooth.common.log.event.OperLogEvent;
import com.sooth.common.satoken.utils.LoginHelper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.http.HttpMethod;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;

import java.util.Collection;
import java.util.Date;
import java.util.Map;
import java.util.StringJoiner;

/**
 * 操作日志记录处理
 *
 * @author Lion Li
 */
@Slf4j
@Aspect
@AutoConfiguration
public class LogAspect {

    @Around("@annotation(log)")
    public Object around(ProceedingJoinPoint joinPoint, Log log) throws Throwable {
        OperLogEvent operLog = new OperLogEvent();
        operLog.setTenantId(LoginHelper.getTenantId());
        String ip = ServletUtils.getClientIP();
        operLog.setOperIp(ip);
        operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255));
        operLog.setOperName(LoginHelper.getUsername());
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        operLog.setMethod(className + "." + methodName);
        operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
        operLog.setTitle(log.title());
        operLog.setMessage(log.message());
        Date date = new Date();
        operLog.setOperTime(date);
        // 是否需要保存request，参数和值
        int limit = log.limit();
        if (limit >= 0) {
            String requestParam = getRequestParam(joinPoint, log.exclude());
            if (limit == 0 || requestParam.length() <= limit) {
                operLog.setOperParam(requestParam);
            } else {
                operLog.setOperParam(requestParam.substring(0, limit));
            }
        }
        try {
            Object proceed = joinPoint.proceed();
            operLog.setCostTime(System.currentTimeMillis() - date.getTime());
            operLog.setStatus(0);
            return proceed;
        } catch (Throwable e) {
            operLog.setStatus(1);
            operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
            throw e;
        } finally {
            SpringUtils.context().publishEvent(operLog);
        }
    }


    private String getRequestParam(JoinPoint joinPoint, String[] excludeParamNames) {
        HttpServletRequest request = ServletUtils.getRequest();
        String requestMethod = request.getMethod();
        if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) {
            return argsArrayToString(joinPoint.getArgs(), excludeParamNames);
        } else {
            Map<String, String> paramsMap = ServletUtils.getParamMap(request);
            if (MapUtil.isNotEmpty(paramsMap)) {
                MapUtil.removeAny(paramsMap, excludeParamNames);
            }
            return JsonUtils.toJsonString(paramsMap);
        }
    }

    /**
     * 参数拼装
     */
    private String argsArrayToString(Object[] paramsArray, String[] excludeParamNames) {
        StringJoiner params = new StringJoiner(",");
        if (ArrayUtil.isEmpty(paramsArray)) {
            return params.toString();
        }
        for (Object o : paramsArray) {
            if (ObjectUtil.isNotNull(o) && !isFilterObject(o)) {
                String str = JsonUtils.toJsonString(o);
                if (ArrayUtil.isNotEmpty(excludeParamNames)) {
                    Dict dict = JsonUtils.parseMap(str);
                    MapUtil.removeAny(dict, excludeParamNames);
                    str = JsonUtils.toJsonString(dict);
                }
                params.add(str);
            }
        }
        return "[" + params + "]";
    }

    /**
     * 判断是否需要过滤的对象。
     *
     * @param o 对象信息。
     * @return 如果是需要过滤的对象，则返回true；否则返回false。
     */
    @SuppressWarnings("rawtypes")
    public boolean isFilterObject(final Object o) {
        Class<?> clazz = o.getClass();
        if (clazz.isArray()) {
            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
        } else if (Collection.class.isAssignableFrom(clazz)) {
            Collection collection = (Collection) o;
            for (Object value : collection) {
                return value instanceof MultipartFile;
            }
        } else if (Map.class.isAssignableFrom(clazz)) {
            Map map = (Map) o;
            for (Object value : map.values()) {
                return value instanceof MultipartFile;
            }
        }
        return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse || o instanceof BindingResult;
    }
}
